/**
 * <p>Title: Engine</p>
 * <p>Description: It make things work!</p>
 * @author Guilherme Mauro Germoglio
 */

import java.awt.*;
import javax.swing.*;

public class Engine {

  // atributes
  private Score score;
  private Spaceship spaceship;
  private Bullet bullet;
  private AsteroidBelt asteroidBelt;
  private JPanel panel;
  private SpaceshipThread spaceshipThread;
  private Ammo ammo;
  private boolean gameOver;
  private boolean gameRunning;
  private boolean paused;
  private boolean menuSelected;
  private JLabel scoreLabel;
  private HallOfFame hallOfFame;
  private final String lineSeparator = System.getProperty("line.separator");
  private int bulletsHit;
  private int bulletsSpent;

// methods

  /**
   * Engine's constructor. Sets the values of some atributes and try to get the
   * high scores, if possible.
   * @param panel The panel it will need.
   */
  public Engine(JPanel panel) {
    this.panel = panel;
    this.gameRunning = false;
    this.menuSelected = false;
    try {
      this.hallOfFame = new HallOfFame();
    }
    catch (Exception e) {
      error(e);
    }
  }

  /**
   * Turns on or off the spaceship's thruster.
   * @param thruster If true, the thruster will be on. If false, it'll be off.
   */
  public void setSpaceshipThruster(boolean thruster) {
    if (!gameOver) {
      spaceship.setThruster(thruster);
    }
  }

  /**
   * Turns on or off the spaceship's rear thruster.
       * @param rearThruster If true, the thruster will be on. If false, it'll be off.
   */
  public void setSpaceshipRearThruster(boolean rearThruster) {
    if (!gameOver) {
      spaceship.setRearThruster(rearThruster);
    }
  }

  /**
   * Set's of the spaceship is turning or not
   * @param turnCounterClockwise True if so, false case not
   */
  public void setSpaceshipTurnCounterClockwise(boolean turnCounterClockwise) {
    if (!gameOver) {
      spaceship.setTurnCounterClockwise(turnCounterClockwise);
    }
  }

  /**
   * Set's of the spaceship is turning or not
   * @param turnClockwise True if so, false case not
   */
  public void setSpaceshipTurnClockwise(boolean turnClockwise) {
    if (!gameOver) {
      spaceship.setTurnClockwise(turnClockwise);
    }
  }

  /**
   * Wow! It shoots!
   */
  public void shoot() {
    if (!gameOver && !paused) {
      bullet = new Bullet(panel);
      ammo.addBullet(bullet);
      bullet.start(spaceship.getX(), spaceship.getY(), spaceship.getDirection());
      bulletsSpent++;
    }
  }

  /**
   * It increases the score. What more?
   */
  public void increaseScore() {
    if (!gameOver) {
      score.increaseScore(5);
      bulletsHit++;
    }
  }

  /**
   * Informs the spaceship it was hit.
   */
  public void spaceshipWasHit() {
    spaceship.hasBeenHit();
  }

  /**
   * It will remove the asteroids that were hit.
   */
  private void removeAsteroids() {
    asteroidBelt.renewAsteroids();
  }

  /**
   * It will mark an asteroid that was hit
   * @param index The asteroid's index
   */
  public void removeAsteroid(int index) {
    asteroidBelt.getAsteroids()[index].hasBeenHit();
    removeAsteroids();
  }

  /**
   * It will eliminate the bullets that are lost in space.
   */
  public void removeBullets() {
    ammo.removeBullets();
  }

  /**
   * It will eliminate a bullet that hit an asteroid.
   * @param index The bullet's index
   */
  public void removeBullet(int index) {
    ammo.getBullets()[index].hitTheTarget();
    removeBullets();
  }

  /**
   * Game over, man. The end. Shows a little message. Refreshes the high scores and saves it.
   */
  public void gameOver() {
    gameOver = true;
    JOptionPane.showMessageDialog(panel,
                                  "                       You've been hit!!",
                                  "Game Over", JOptionPane.PLAIN_MESSAGE);
    refreshHighScores();
    saveHighScores();
  }

  /**
   * Refreshes high scores. Gets the current score, checks if can be at hall of fame. If so
   * makes it possible, asking the name of the player.
   */
  private void refreshHighScores() {
    double scoreWannaBe = score.getScore();
    if (hallOfFame.canBeAtHallOfFame(scoreWannaBe)) {
      String playerName = JOptionPane.showInputDialog(panel,
          "You've got a high score!" + lineSeparator + "Enter your name: ",
          "Well done...", JOptionPane.PLAIN_MESSAGE);
      if ( (playerName == null) || playerName.equals(""))
        playerName = "Anonymous";
      addHighScore(playerName, scoreWannaBe, bulletsHit, bulletsSpent);
    }
    showHighScores();
  }

  /**
   * Adds a new high score at the Hall of fame
   * @param playerName The player who got a new high score
   * @param scoreWannaBe The player's score
   * @param bulletsHit The amount of bullets which really hit asteroids
   * @param bulletsSpent The number of bullets spent
   */
  private void addHighScore(String playerName, double scoreWannaBe,
                            int bulletsHit, int bulletsSpent) {
    HighScore highScore = new HighScore(playerName, scoreWannaBe, bulletsHit,
                                        bulletsSpent);
    hallOfFame.add(highScore);
    if (hallOfFame.getSize() > 5)
      hallOfFame.remove(5);
  }

  /**
   * Shows the high scores dialog.
   */
  public void showHighScores() {
    JOptionPane.showMessageDialog(panel, hallOfFame.toString(), "High scores",
                                  JOptionPane.PLAIN_MESSAGE);
  }

  /**
   * Records on disk the high scores.
   */
  public void saveHighScores() {
    try {
      hallOfFame.recordInDisk();
    }
    catch (Exception e) {
      error(e);
    }
  }

  /**
   * Creates a new game.
   * @param panel The JPanel it'll be used.
   * @param scoreLabel The label where the score will be written at.
   * @param asteroidBelt The new asteroid belt
   * @param spaceship The brand new spaceship
   * @param ammo Where all the shells go
   * @param resetScore Is it needed to reset the score?
   */
  public synchronized void newGame(JPanel panel, JLabel scoreLabel,
                                   AsteroidBelt asteroidBelt,
                                   Spaceship spaceship, Ammo ammo,
                                   boolean resetScore) {
    if (gameRunning)
      endGame();
    this.gameRunning = true;
    this.scoreLabel = scoreLabel;
    this.panel = panel;
    this.ammo = ammo;
    this.asteroidBelt = asteroidBelt;
    this.spaceship = spaceship;
    if (resetScore) {
      this.score = new Score(scoreLabel);
      bulletsHit = 0;
      bulletsSpent = 0;
    }
    this.gameOver = false;
    this.spaceshipThread = new SpaceshipThread(panel, spaceship);
    spaceshipThread.start();
    asteroidBelt.start(panel);
    score.draw(Color.BLACK);
  }

  /**
   * Ends the game. Ends the threads and things like that.
   */
  public void endGame() {
    asteroidBelt.endGame();
    spaceship.hasBeenHit();
    panel.update(panel.getGraphics());
  }

  /**
   * Returns if the game is already running or not
   * @return True if so, false case not
   */
  public boolean isRunning() {
    return gameRunning;
  }

  /**
   * It pauses the game.
   */
  public void pauseGame() {
    if (!paused) {
      asteroidBelt.pauseThreads();
      spaceshipThread.setPause(true);
      ammo.pauseThreads();
    }
  }

  /**
   * It resumes the game
   */
  public void depauseGame() {
    if (paused ^ menuSelected) {
      asteroidBelt.depauseThreads();
      spaceshipThread.setPause(false);
      ammo.depauseThreads();
    }
  }

  /**
   * Tells the engine the menu was selected.
   */
  public void menuSelected() {
    menuSelected = true;
  }

  /**
   * Tells the engine the menu was deselected
   */
  public void menuDeselected() {
    menuSelected = false;
  }

  /**
   * Make things work when the pause button is pressed. If it's already paused
   * resumes - if the game is running, it pauses.
   */
  public void pressedPauseButton() {
    if (paused) {
      depauseGame();
      paused = false;
    }
    else {
      pauseGame();
      paused = true;
    }
  }

  /**
   * Shows a message dialog in case of error
   * @param e The error!
   */
  public void error(Exception e) {
    JOptionPane.showMessageDialog(panel, e.getMessage(), "A fuckin' error...",
                                  JOptionPane.ERROR_MESSAGE);
  }

  /**
   * Shows the help dialog.
   */
  public void showHelp() {
    JOptionPane.showMessageDialog(panel, getHelp(), "Need help?",
                                  JOptionPane.PLAIN_MESSAGE);
  }

  /**
   * Constructs the help text.
   * @return The help text
   */
  private String getHelp() {
    String help = "Your Mission:" + lineSeparator + lineSeparator +
        "The outer space is so dull. You've got nothing" + lineSeparator +
        "to do... So, why don't destroy everything that" + lineSeparator +
        "moves?" + lineSeparator + lineSeparator + "Controls:" + lineSeparator +
        lineSeparator + "Thruster:  Upper Arrow" +
        lineSeparator + "Rear thruster:  Down Arrow" + lineSeparator +
        "Rotate counter clockwise:  Left Arrow" + lineSeparator +
        "Rotate clockwise:  Right Arrow" + lineSeparator +
        "Shoot:  Space" + lineSeparator + "Pause:  P" + lineSeparator +
        lineSeparator +
        "Have fun!   = )";
    return help;
  }

  /**
   * Shows the about dialog.
   */
  public void showAbout() {
    JOptionPane.showMessageDialog(panel, getAbout(), "About JAsteroids",
                                  JOptionPane.PLAIN_MESSAGE);
  }

  /**
   * Constructs the about text.
   * @return The about text
   */
  private String getAbout() {
    String about = "JAsteroids 1.00" + lineSeparator + "The Java Asteroids" +
        lineSeparator + lineSeparator +
        "Written by Guilherme Mauro \"Germoglio\"" + lineSeparator +
        lineSeparator + "Thanks to:" + lineSeparator +
        "Daniel Fireman, Alisson SkyWilker, Mestre Danilo," + lineSeparator +
        "Arthur The Barbarian, Yuska, Vinicius, Walter, Lord " + lineSeparator +
        "Waldermort, Mestre Ivo, T. and all who dared me" + lineSeparator +
        "saying \"You can't do this.\"" + lineSeparator + lineSeparator +
        "Bugs? Comments? Money?" + lineSeparator + "guiga@dsc.ufcg.edu.br";
    return about;
  }

  /**
   * Makes two asteroids bounce! I mean, two asteroids, specified by their indexes
   * will change their direction because they collided.
   * @param asteroid_A_index The first asteroid index.
   * @param asteroid_B_index The sencond asteroid index.
   */
  public synchronized void asteroidHitsAsteroid(int asteroid_A_index, int asteroid_B_index) {
    Asteroid asteroid_A = asteroidBelt.getAsteroids()[asteroid_A_index];
    Asteroid asteroid_B = asteroidBelt.getAsteroids()[asteroid_B_index];
    double asteroid_A_X_velocity = asteroid_A.get_X_velocity();
    double asteroid_A_Y_velocity = asteroid_A.get_Y_velocity();
    double asteroid_B_X_velocity = asteroid_B.get_X_velocity();
    double asteroid_B_Y_velocity = asteroid_B.get_Y_velocity();
    asteroid_A.collidedWith(asteroid_B_index);
    asteroid_B.collidedWith(asteroid_A_index);
    asteroid_A.set_X_velocity(asteroid_B_X_velocity);
    asteroid_A.set_Y_velocity(asteroid_B_Y_velocity);
    asteroid_B.set_X_velocity(asteroid_A_X_velocity);
    asteroid_B.set_Y_velocity(asteroid_A_Y_velocity);
  }
}